﻿using System;
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using HighDiveEngine.IoEngines;
using HighDiveEngine.Model;
using HighDiveEngine.Resources;

namespace HighDiveJudge.Resources
{
    public class TcpHandler
    {
        #region Properties

        private TcpEngine _tcpEngine;
        private Thread _serverThread;
        private string _ipAddress;
        private static readonly TcpHandler _instance = new TcpHandler();

        //judge application events
        public static event MethodInvoker EventContestEnded;
        public static event MethodInvoker EventJudgeLoginSuccessful;
        public static event MethodInvoker EventJudgeLoginRefused;
        public static event DelegateVoidString EventConnectionFailed;
        public static event MethodInvoker EventIsAlive;
        public static event DelegateVoidDive EventJudgeNewDiveReceived;
        public static event DelegateVoidDiveScoreDive EventJudgeNewDiveScoreReceived;        

        /// <summary>
        /// The machines listen Ip Adress. Changing this will start a new Tcp listening thread
        /// </summary>
        public string IpAddress
        {
            get { return _ipAddress; }
            set
            {
                if (_serverThread != null && _serverThread.IsAlive)
                {
                    _serverThread.Abort();
                    _serverThread.Join();
                }
                _ipAddress = value;
                _tcpEngine = new TcpEngine(value, ReceiveMessage, _connectionFailed);
                _serverThread = new Thread(_tcpEngine.TcpServerThread) { IsBackground = true };
                _serverThread.Start();
            }
        }

        public static TcpHandler Instance { get { return _instance; } }

        #endregion

        #region Constructor
        private TcpHandler()
        {
            _setIp();
            //IpAddress = "127.0.0.2:9058";
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Called by the constructor to set the default ip adress
        /// </summary>
        private void _setIp()
        {
            IPHostEntry host;
            string localIP = "?";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    localIP = ip.ToString() + ":9058";
                }
            }
            IpAddress = localIP;
        }

        /// <summary>
        /// Called when TcpEngine reports a connection fail
        /// </summary>
        /// <param name="ip">The ip TcpEngine tried to connect to</param>
        private void _connectionFailed(string ip)
        {
            if (EventConnectionFailed != null)
                EventConnectionFailed(ip);
        }

        #endregion

        #region Send Methods

        /// <summary>
        /// Send a string to TcpEngine
        /// The string should start with ip:port@ followed by the message
        /// </summary>
        /// <param name="message"></param>
        public void SendMessage(string message)
        {
            _tcpEngine.TcpTxMessage(message);
        }

        /// <summary>
        /// Sends an IsAlive message to an ip
        /// </summary>
        /// <param name="adminIpAdress">The destination ip</param>
        /// <param name="idString">The judge's IdString</param>
        public void SendIsAliveToAdmin(string adminIpAdress, string idString)
        {
            _tcpEngine.TcpTxMessage(adminIpAdress + "@JudgeAppIsAlive:" + idString);
        }

        /// <summary>
        /// Sends a login request to an ip
        /// </summary>
        /// <param name="adminIpAdress">The destination ip</param>
        /// <param name="idString">The judge's IdString</param>
        /// <param name="listenPort">The judge's listen port</param>
        public void SendLoginRequestToAdmin(string adminIpAdress, string idString, int listenPort)
        {
            _tcpEngine.TcpTxMessage(adminIpAdress + "@JudgeAppLogin:" + idString + ":" + listenPort);
        }

        /// <summary>
        /// Sends a score belonging to the specified dive to an ip
        /// </summary>
        /// <param name="adminIpAdress">The destination ip</param>
        /// <param name="idString">The judge's IdString</param>
        /// <param name="dive">A dive including the score which to send</param>
        public void SendScoreToAdmin(string adminIpAdress, string idString, Dive dive)
        {
            _tcpEngine.TcpTxMessage(adminIpAdress + "@JudgeAppUpdateScore:" + idString + ":" + dive.Diver.IdNumber + ":" +
                                    dive.Diver.StartNumber + ":" + dive.DiveNumber + ":" + dive.DiveCode + ":" +
                                    Convert.ToDouble(dive.DiveScoreList[0].Score).ToString(CultureInfo.InvariantCulture));
        }

        /// <summary>
        /// Sends a Dive Receive Error to an ip
        /// This should be called when there is an already active dive and a new dive is received
        /// </summary>
        /// <param name="_adminIpAdress">The destination ip</param>
        /// <param name="_idString">The judge's IdString</param>
        public void SendDiveReceiveErrorToAdmin(string _adminIpAdress, string _idString)
        {
            _tcpEngine.TcpTxMessage(_adminIpAdress + "@JudgeAppDiveReceiveError" + ":" + _idString);
        }

        /// <summary>
        /// Sends a log out notification to an ip.
        /// This should be called whenever the judge closes his program or logs out.
        /// </summary>
        /// <param name="_adminIpAdress">The destination ip</param>
        /// <param name="_idString">The judge's IdString</param>
        public void SendLogoutNotificationToAdmin(string _adminIpAdress, string _idString)
        {
            _tcpEngine.TcpTxMessage(_adminIpAdress + "@JudgeAppLogout" + ":" + _idString);
        }

        #endregion

        #region Receive Methods

        /// <summary>
        /// Called when a message is received by TcpEngine
        /// </summary>
        /// <param name="message">The message received. Should start with ip:port@ followed by the message</param>
        private void ReceiveMessage(string message)
        {
            if (new Regex(@"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]{4,5}@.*$").IsMatch(message))
            {
                string[] msg = message.Split('@');

                if (msg[1] == "AdminContestEnded")
                {
                    receiveAdminContestEnded();
                }
                else if (msg[1] == "AdminLoginSuccessful")
                {
                    receiveAdminLoginSuccessful();
                }
                else if (msg[1] =="AdminLoginRefused")
                {
                    receiveAdminLoginRefused();
                }
                else if (new Regex(@"^AdminNewDive:[0-9]{10}:([a-zA-Z åäöüÅÄÖ]*:){2}([0-9]*:){2}[0-9]{3,4}[a-zA-Z]:([1|3|5|7]|[1][0]):[0-9]*([\.][0-9]+)?$").IsMatch(msg[1]))
                {
                    receiveAdminNewDive(msg);
                }
                else if (new Regex(@"^AdminUpdateScore:[0-9]{10}:([a-zA-Z åäöüÅÄÖ]*:){2}([0-9]*:){2}[0-9]{3,4}[a-zA-Z]:([1|3|5|7]|[1][0]):[0-9]*([\.][0-9]+)?:[a-zA-Z0-9]{5}:([1][0]|([0-9](\.[05])?))$").IsMatch(msg[1]))
                {
                    receiveAdminUpdateScore(msg);
                }
                else if (msg[1] == "IsAlive")
                {
                    receiveIsAlive();
                }
                else if (msg[1] == "AdminAppExited")
                {
                    _connectionFailed(msg[0]);
                }
            }
            else
            {
                throw new TcpHandlerException("Unknown message received");
            } 
        }

        private void receiveAdminContestEnded()
        {
            if (EventContestEnded != null) 
                EventContestEnded();
        }

        /// <summary>
        /// Called when an IsAlive message is received
        /// </summary>
        private void receiveIsAlive()
        {
            if (EventIsAlive != null)
                EventIsAlive();
        }

        /// <summary>
        /// Called when a AdminUpdateScore message is received
        /// </summary>
        /// <param name="msg">The received message</param>
        private void receiveAdminUpdateScore(string[] msg)
        {
            string[] splitmsg = msg[1].Split(':');
            Dive dive = new Dive()
            {
                Diver =
                    new Diver()
                    {
                        IdNumber = splitmsg[1],
                        FirstName = splitmsg[2],
                        LastName = splitmsg[3],
                        StartNumber = Convert.ToInt32(splitmsg[4])
                    },
                DiveNumber = Convert.ToInt32(splitmsg[5]),
                DiveCode = splitmsg[6],
                DiveHeight = Convert.ToInt32(splitmsg[7]),
                Difficulty = Convert.ToDouble(splitmsg[8], CultureInfo.InvariantCulture)
            };

            DiveScore score = new DiveScore()
            {
                Judge = new Judge() {IdString = splitmsg[9]},
                Score = Convert.ToDouble(splitmsg[10], CultureInfo.InvariantCulture)
            };

            if (EventJudgeNewDiveScoreReceived != null)
                EventJudgeNewDiveScoreReceived(score, dive);
        }

        /// <summary>
        /// Called when a AdminNewDive message is received
        /// </summary>
        /// <param name="msg">The received message</param>
        private void receiveAdminNewDive(string[] msg)
        {
            string[] splitmsg = msg[1].Split(':');
            Dive dive = new Dive()
            {
                Diver =
                    new Diver()
                    {
                        IdNumber = splitmsg[1],
                        FirstName = splitmsg[2],
                        LastName = splitmsg[3],
                        StartNumber = Convert.ToInt32(splitmsg[4])
                    },
                DiveNumber = Convert.ToInt32(splitmsg[5]),
                DiveCode = splitmsg[6],
                DiveHeight = Convert.ToInt32(splitmsg[7]),
                Difficulty = Convert.ToDouble(splitmsg[8], CultureInfo.InvariantCulture)
            };

            if (EventJudgeNewDiveReceived != null)
                EventJudgeNewDiveReceived(dive);
        }

        /// <summary>
        /// Called when a AdminLoginRefused message is received
        /// </summary>
        private void receiveAdminLoginRefused()
        {
            if (EventJudgeLoginRefused != null)
                EventJudgeLoginRefused();
        }

        /// <summary>
        /// Called when a AdminLoginSuccessful message is received
        /// </summary>
        private void receiveAdminLoginSuccessful()
        {
            if (EventJudgeLoginSuccessful != null)
                EventJudgeLoginSuccessful();
        }

        #endregion
    }
}
